#####################################################################
# File:         Component.py
# Product:      KnowledgeEngine
#
# Copyright:    Copyright (c) 2002, Faulkner Ltd.
# Author:       Brian Oliver
#
# Description:  This file defines the abstract Comopnent class, the 
#               base class of all components that are contained
#               with in Models and used for assessment.
#
# See Also:     GenericObject, Model, ComponentContainer
#####################################################################

#####################################################################
## Knowledge Engine Library Imports
from Common import *
from GenericObject import GenericObject, GenericObjectInfo
from ComponentContext import ComponentContext
from DependantsManager import DependantsManager
from Products.KnowledgeEngine.Model.Model import Model
from Products.KnowledgeEngine.Library.Library import Library
from Products.KnowledgeEngine.Environment.Environment import Environment
from PropertyDefinition import *
from PropertyTypes import *
from PropertyFormats import *
from PropertyStructures import *
from Assessible import Assessible
from Report import *
from KERFProcessor import *
from KERLProcessor import *

#####################################################################
## Zope Library Imports
from Globals import DTMLFile
from ImageFile import ImageFile
from Acquisition import *

#####################################################################
## Python Library Imports
# (none)

#####################################################################
## ComponentInfo Class Definition
class ComponentInfo(GenericObjectInfo):
    """
        Defines the class info methods, defining information about
        the class without needing an instance of the class
    """
    
    #---------------------------------------------------------------#
    
    def getClassName(self):
        """
            Returns the name of the class for which 
            we are providing information.
        """
        return 'Component'

    #---------------------------------------------------------------#

    def getDisplayName(self):
        """
            Return a friendly name for the class instances
            (this is used for displaying in lists etc)
        """
    
        return "Component"
        
    #---------------------------------------------------------------#

    def getIdentityType(self):
        """
            ABSTRACT: Return the identity type.  
            
            This is a short string used for prefixing object identities
        """
        
        return "component"

    #---------------------------------------------------------------#

    def isInstanceOf(self, className):
        """
            Returns if the class is an instance of the specified
            className, or implements the interface specified 
            by the className
        """

        return className == 'Component' or GenericObjectInfo.isInstanceOf(self, className)
        
    #---------------------------------------------------------------#

    def isPermittedInContainer(self, containerClassInfo, containerInstance = None, instance=None):
        """
            ABSTRACT: Returns if class instances (instance) is permitted in 
            the specified container (containerInstance)
            
            NOTE: This is from this class' instance perspective.
        """
        
        #DEFAULT RULE: Components are permitted in ComponentContainers OR Libraries
        return containerClassInfo.isInstanceOf('ComponentContainer') or containerClassInfo.isInstanceOf('Library')

    #---------------------------------------------------------------#

    def isChildPermitted(self, childClassInfo, childInstance = None):
        """
            Returns if the specified child is permitted as a 
            child of the class' instances.
            
            NOTE: This is from this class' instance perspective.
            
            For component containers, this always overrides what the
            return value from childClassInfo.isPermittedInContainer(...)
        """
        
        #DEFAULT RULE: Components can't have Children (only ComponentContainers can have children)
        return false
    
    #---------------------------------------------------------------#

    def onConfigureProperties(self):
        """
            Automatically called by the Knowledge Engine once
            the class has been loaded.  Use this method to register 
            class specific properties using the PropertyReflection interface.
            
            If this class is sub-classed, a call must be made to
            the super-class to ensure super-class properties are
            correctly registered
            
            NOTE: This method is called before ClassInfo.onAfterStartup()            
        """

        #initialise the super-class properties for reflection
        GenericObjectInfo.onConfigureProperties(self)
        
        #add the 'guidance' property        
        self.addPropertyDefinition(PropertyDefinition(identity="guidance", displayName="Guidance", description="Information to help the end-user with this component", defaultValue="", propertyType=pt_STRING, propertyFormat=pf_HTML, propertyStructure=ps_ATOMIC))        

        #add the 'order' property        
        self.addPropertyDefinition(PropertyDefinition(identity="order", displayName="Order", description="The relative position of the component with in it's container", defaultValue=1.0, propertyType=pt_FLOAT, propertyFormat=pf_NATIVE, propertyStructure=ps_ATOMIC, isForConstructor=false, isForPropertyEditor=false))        

        #add the 'showif' property        
        self.addPropertyDefinition(PropertyDefinition(identity="showif", displayName="Show If...", description="The Knowledge Engine Rule Language Expression (KERL) that defines when this component should be displayed", defaultValue="", propertyType=pt_STRING, propertyFormat=pf_TEXT_SINGLELINE, propertyStructure=ps_ATOMIC))        

        #add the 'reference' property        
        self.addPropertyDefinition(PropertyDefinition(identity="reference", displayName="Reference", description="Text that provides external reference information that was used to define this component", defaultValue="", propertyType=pt_STRING, propertyFormat=pf_TEXT_SINGLELINE, propertyStructure=ps_ATOMIC))        

        #add the 'content' property        
        self.addPropertyDefinition(PropertyDefinition(identity="content", displayName="Content", description="Text to be displayed with this component", defaultValue="", propertyType=pt_STRING, propertyFormat=pf_HTML, propertyStructure=ps_ATOMIC))        

        #add the 'reportTitle' property        
        self.addPropertyDefinition(PropertyDefinition(identity="reportTitle", displayName="Report Title", description="The title to be used when this component is displayed in a report. (leave blank to use the component title)", defaultValue="", propertyType=pt_STRING, propertyFormat=pf_HTML_SINGLELINE, propertyStructure=ps_ATOMIC))        

        #add the 'reportContent' property        
        self.addPropertyDefinition(PropertyDefinition(identity="reportContent", displayName="Report Content", description="The following content will appear in the report below the Report Title.", defaultValue="", propertyType=pt_STRING, propertyFormat=pf_HTML, propertyStructure=ps_ATOMIC))        
        
    #---------------------------------------------------------------#

#####################################################################
## Component Class Definition
class Component(GenericObject, DependantsManager):
    """
        Base class for all KnowledgeEngine Components
    """

    #---------------------------------------------------------------#

    #ABSTRACT TRANSIENT: a ClassInfo implementation instance to provide info about this class
    _CLASSINFO = ComponentInfo()

    #---------------------------------------------------------------#

    # Custom zope manage_preview to permit previews of components.
    preview_html = DTMLFile('dtml/preview', globals())

    # Component Guidance Methods
    guidance_html = DTMLFile('dtml/guidance', globals())
    guidance_link_html = DTMLFile('dtml/guidance_link', globals())
    guidance_icon_small_gif = ImageFile('www/guidance.icon.small.gif', globals())
    guidance_background_gif = ImageFile('www/guidance.background.gif', globals())

    #---------------------------------------------------------------#
    # Volatile Attributes
    _v_ShowIfKERL = None        #internal representation of the showif KERL
    _v_Model = None             #the model to which the component belongs
    
    #---------------------------------------------------------------#

    def __init__(self, id, identity):
        "Constructor for the component"        
        
        GenericObject.__init__(self, id, identity)
                
        self._v_ShowIfKERL = None
        self._v_Model = None
                
        #perform onAfterInstanciation processing
        self.onAfterInstanciation()

    #---------------------------------------------------------------#

    def onAfterInstanciation(self):
        """
            Handle Knowledge Engine specific actions after this object 
            has been loaded into memory.  
            
            This is useful for performing backwards compatibility
        """

        #perform default onAfterInstanciation processing
        GenericObject.onAfterInstanciation(self)
        
        #if the reportContent attribute is missing, we should now add it with the regular content as the default
        #this is so that we can maintain backwards compatibility with old reports
        if not hasattr(self, "reportContent"):
            if hasattr(self, "content"):
                #set the default reportContent to be the standard content
                self.reportContent = self.content
            else:
                self.reportContent = ""
                
        ###print "DEBUG: Component.onAfterInstanciation: Completed"

    #---------------------------------------------------------------#

    def getIdentity(self, componentContext = None):
        "Returns the unique identity of the object"

        #is the component in a context?
        if componentContext != None:
            return componentContext.getIdentity(self)
        else:
            return GenericObject.getIdentity(self)

    #---------------------------------------------------------------#

    def getParent(self, componentContext = None):
        """
            Return the parent container that this component is within.
            If a component context has been provided and the
                real parent is a library, return the component
                defining the context (context must be defined)
            If a component context has been provided but the real
                parent is another componentm return the actual parent.
                
            Alternatively, return the actual parent of the component.
        """

        #has a context been provided?
        if componentContext == None:
            #return the components actual parent
            return GenericObject.getParent(self)

        else:
            #is the actual parent of the component a Library?
            if isinstance(GenericObject.getParent(self), Library):
                
                #return the proxy component that defines the context
                return componentContext.getComponent()

            else:
                #return the actual parent
                return GenericObject.getParent(self)

    #---------------------------------------------------------------#

    def getParents(self, componentContext = None):
        """
            Returns a list of parent Component Containers
            (not including the Model, which isn't a Component
             Container anyway!)
        """

        if isinstance(self.getParent(componentContext), Model):
            return []
        
        if isinstance(self.getParent(componentContext), Library):
            return []
        
        else:
            return [self.getParent(componentContext)] + self.getParent(componentContext).getParents(componentContext)

    #---------------------------------------------------------------#

    def isDescendant(self, ancestor):
        """
            Returns if the component is a descendant of the specified ancestor
            (also returns true if self and the ancestor are the same!)
        """
        
        return ancestor in self.getParents() or self is ancestor

    #---------------------------------------------------------------#

    def getBaseParent(self, componentContext = None):
        """
            Returns the parent component container that is the
            immediate child of the model/library in which this component
            is contained.

            RETURNS: None if the parent of this component is a Model/Library
        """

        #get all of the parents (until the Model or Library)
        parents = self.getParents(componentContext)
        
        if len(parents) == 0:
            return None
        else:
            return parents[len(parents)-1]

    #---------------------------------------------------------------#

    def getPage(self):
        """
            Returns the page on which this component is located
            (taking promotion into account)
            
            May return self if the component has been promoted to
            the top-level!
        """
        
        #create a stack of possible pages... starting with this component
        componentStack = [self] + self.getParents()
        page = None
        
        while page is None:
            
            #pop the top-level parent
            parent = componentStack.pop()
        
            #is this the last parent?
            if len(componentStack) == 0:
                page = parent
            else:
                #is the parent a section?
                if parent.isInstanceOf("Section"):

                    #is the section promoting children?
                    if parent.getIsPromotingChildren():
                        #we should step to the next parent down, as 
                        #we need to promote it.
                        pass
                        
                    else:
                        #the section isn't promoting, so it's the top-level page
                        page = parent
                    
                else:
                    #as it's not a section, it must be a page
                    page = parent
        
        return page
    
    #---------------------------------------------------------------#

    def getModel(self, componentContext = None):
        """
            Returns the Model in which this component is located.
            If supplied a context, the component context is used.
            If not supplied a context, and in a library, None is returned
            Alternatively, the Model of the parent is returned.
        """
        
        #is the component in a context?
        if componentContext != None:
            return componentContext.getModel()

        elif self.isInLibrary():
            return None
        
        else:
            #have we already worked out the model
            if self._v_Model == None:

                #get the parent of this component
                parent = self.getParent()

                #is the parent a Model?
                if isinstance(parent, Model):
                    self._v_Model = parent
                    
                else:
                    self._v_Model = parent.getModel()

            #returned the cached model
            return self._v_Model
        
    #---------------------------------------------------------------#

    def getGuidance(self):
        "Returns the Guidance / Help for the object"        
        return self.guidance

    #---------------------------------------------------------------#

    def setGuidance(self, guidance):
        "Set the Guidance / Help for the object"        
        self.guidance = guidance
        
    #---------------------------------------------------------------#

    def getShowIf(self):
        "Returns the ShowIf expression of the component"        
        return self.showif

    #---------------------------------------------------------------#

    def setShowIf(self, showif):
        "Set the ShowIf expression of the component"        
        self.showif = showif

    #---------------------------------------------------------------#

    def hasShowIf(self):
        "Return if the component has a locally defined showif"
        return self.showif
        
    #---------------------------------------------------------------#

    def getReference(self):
        "Returns the user defined Reference for the component"        
        return self.reference

    #---------------------------------------------------------------#

    def setReference(self, reference):
        "Set the user defined Reference for the component"        
        self.reference = reference
                
    #---------------------------------------------------------------#

    def getState(self):
        "Placeholder method should be redefined by each component"        
        return None
                
    #---------------------------------------------------------------#
        
    def getContent(self):
        "Returns the content the component"        
        return self.content

    #---------------------------------------------------------------#

    def setContent(self, content):
        "Set the content the component"        
        self.content = content

    #---------------------------------------------------------------#

    def hasContent(self):
        "Returns if the object has content"        
        return len(self.getContent()) > 0
                
    #---------------------------------------------------------------#
        
    def getTitleOrContent(self):
        """
            Returns the component title (if it has one) or 
            alternatively returns the component content
        """        
        
        if self.hasTitle():
            return self.getTitle()
        else:
            return self.getContent()
                
    #---------------------------------------------------------------#
        
    def getContentOrTitle(self):
        """
            Returns the component content (if it has some) or 
            alternatively returns the component title
        """        
        
        if self.hasContent():
            return self.getContent()
        else:
            return self.getTitle()
        
    #---------------------------------------------------------------#

    def getReportTitle(self):
        "Returns the Report Title the component"        
        return self.reportTitle

    #---------------------------------------------------------------#

    def setReportTitle(self, reportTitle):
        "Set the Report Title for the component"        
        self.reportTitle = reportTitle
                
    #---------------------------------------------------------------#
        
    def getReportContent(self):
        "Returns the reportContent the component"        
        return self.reportContent

    #---------------------------------------------------------------#

    def setReportContent(self, reportContent):
        "Set the reportContent the component"        
        self.reportContent = reportContent

    #---------------------------------------------------------------#

    def hasReportContent(self):
        "Returns if the object has reportContent"        
        return len(self.getReportContent()) > 0

    #---------------------------------------------------------------#

    def onBeforeIdentityChange(self):
        """
            Handle Knowledge Engine specific actions before the 
            identity of this object is changed.
            
            Called By GenericObject.onAfterClone
        """

        #perform normal onBeforeIdentityChange processing
        GenericObject.onBeforeIdentityChange(self)

        try:
            ## The following code prevents duplicate Component cache entries 
            ## being created in an IndexedGenericContainers (models/libraries)
            ## when a Component is copied between IndexedGenericContainers.  
            ##
            ## That is, one Component with two different Identities in the 
            ## target IndexedGenericContainer.
            ##
            ## The problem is caused by the Zope platform performing a copy,
            ## then an add before the clone callback is called.  
            ## The process is as follows; 
            ## 1. copy is created (without changing Identities... no problem)
            ## 2. copy is added into the target indexed container, which is different
            ##    from the indexed container being copied from.  Because the component
            ##    doesn't exist in the new indexed container, the component is
            ##    added to the cache, with the original identity!
            ## 3. the onAfterClone callback is performed, which allocates 
            ##    new identities to the copies.  The component is then added to 
            ##    the target indexed container cache (again), but this time with a 
            ##    new identity... there you go, one component, two entries in an 
            ##    indexed container cache.
            
            #is the component in a library?
            if self.isInLibrary():
                indexedcontainer = self.getLibrary()
            else:                       
                indexedcontainer = self.getModel()

            #get the component with this component's identity from the model
            component = indexedcontainer.getComponent(self.getIdentity())

            #is the component with this component's identity, this component?
            #(if it is, this component is already in the indexedcontainer, so remove it)
            if self == component:
                #remove the component, as it will be added with a new identity
                #by the Component.onAfterClone
                indexedcontainer.onBeforeDeleteComponent(self)

        except:
            pass        

    #---------------------------------------------------------------#

    def onAfterIdentityChange(self, identityMappings):
        """
            Handle Knowledge Engine specific actions after the 
            identity of this object has changed.
            
            The identityMappings dictionary holds 
            mappings from oldIdentities -> newIdentities
            (including this object)
                        
        """

        #perform default processing
        GenericObject.onAfterIdentityChange(self, identityMappings)

        #is the component in a library?
        if self.isInLibrary():
            indexedContainer = self.getLibrary()
        else:
            indexedContainer = self.getModel()
        
        #resolve the showif rule (if the component has one).
        if self.getShowIfKERL() != None:
        
            #resolve the showif kerl
            showIfKERL = updateKERLIdentities(self.getShowIfKERL(), identityMappings, indexedContainer)

            #resolve the showif
            ###print "DEBUG: Component.onAfterIdentityChange showif \nbefore %s\nafter %s" % (self.getShowIf(), repr(showIfKERL))        

            if showIfKERL != None:
                self.setShowIf(repr(showIfKERL))
            else:
                self.setShowIf("")        
                
        #resolve report KERFs
        self.setReportTitle(updateKERF(self.getReportTitle(), identityMappings))
        self.setReportContent(updateKERF(self.getReportContent(), identityMappings))
                
        ###print "DEBUG: Component.onAfterIdentityChange mappings %s" % (repr(identityMappings),)

        ###print "DEBUG: Component.onAfterIdentityChange: Completed"
        
    #---------------------------------------------------------------#

    def onAfterClone(self, identityMappings = {}, componentContext = None):
        """
            Handle Knowledge Engine specific actions after this component
            has been cloned
        """

        #perform normal onAfterClone processing
        GenericObject.onAfterClone(self, identityMappings)

        #notify the model that the component has been added (it will have a new identity!)
        try:
            if self.isInLibrary():
                self.getLibrary().onAfterAddComponent(self)            

                #get the parents of the component (we need to find the one that is in directly in the library)
                parents = self.getParents()

                #is this component directly placed in the library or is it in a container in the library?
                #if the component is in a container within the library, we need to find the libraryitems (hence models)
                #that are using the container.  once we have done this we can add this new component as an indexed
                #component in those models.
                if len(parents) > 0:
                    #get the libraryitems that use the parent
                    libraryitems = parents[len(parents)-1].getDependants("libraryitem")

                    #add this component to each of the models for each of the libraryitems                        
                    for component in libraryitems.values():
                        component.getModel().onAfterAddComponent(self, component.getComponentContext())
            else:
                self.getModel().onAfterAddComponent(self, componentContext)            
        except:
            pass

        #register the dependancies
        self.registerDependencies(componentContext)
        
        ###print "DEBUG: Component.onAfterClone: Completed"

    #---------------------------------------------------------------#

    def onAfterAdd(self, componentContext = None):
        """
            Handle Knowledge Engine specific actions after this component
            has been added to a container.
        """

        ###print "DEBUG: Component.onAfterAdd: Commenced"

        #perform normal onAfterAdd processing
        GenericObject.onAfterAdd(self)

        #notify the model that the component has been added
        try:
            #has a componentContext been provided?
            if componentContext != None:                
                #called from LibraryItem proxy
                ###print "DEBUG: Component.onAfterAdd: Added using component context"
                componentContext.getModel().onAfterAddComponent(self, componentContext)                
            else:
                #no component context           
                if self.isInLibrary():
                    #add the component to the library reference
                    self.getLibrary().onAfterAddComponent(self)

                    #get the parents of the component (we need to find the one that is in directly in the library)
                    parents = self.getParents()

                    #is this component directly placed in the library or is it in a container in the library?
                    #if the component is in a container within the library, we need to find the libraryitems (hence models)
                    #that are using the container.  once we have done this we can add this new component as an indexed
                    #component in those models.
                    if len(parents) > 0:
                        #get the libraryitems that use the parent
                        libraryitems = parents[len(parents)-1].getDependants("libraryitem")

                        #add this component to each of the models for each of the libraryitems                        
                        for component in libraryitems.values():
                            component.getModel().onAfterAddComponent(self, component.getComponentContext())
                else:
                    self.getModel().onAfterAddComponent(self, componentContext)
        except:
            pass

        #register the dependancies
        self.registerDependencies(componentContext)
        
        ###print "DEBUG: Component.onAfterAdd: Completed"

    #---------------------------------------------------------------#

    def onBeforeDelete(self, componentContext = None):
        """
            Handle Knowledge Engine specific actions before this component is deleted
        """

        #is the component assessible? (remove assessment state for the component)
        if self.isInstanceOf('Assessible'):
            self.removeState(componentContext)

        #deregister the current dependancies
        self.deregisterDependencies(componentContext)
        
        #perform normal onBeforeDelete processing
        GenericObject.onBeforeDelete(self)

        #notify the model that the component is about to be deleted
        try:
            #has a componentContext been provided?
            if componentContext != None:
                #called from LibraryItem proxy
                componentContext.getModel().onBeforeDeleteComponent(self, componentContext)
            else:
                if self.isInLibrary():
                    #remove the item from the library
                    self.getLibrary().onBeforeDeleteComponent(self)

                    #get the parents of the component (we need to find the one that is in directly in the library)
                    parents = self.getParents()

                    #is this component directly placed in the library or is it in a container in the library?
                    #if the component is in a container within the library, we need to find the libraryitems (hence models)
                    #that are using the container.  once we have done this we can remove this component as an indexed
                    #component in those models.
                    if len(parents) > 0:
                        #get the libraryitems that use the parent
                        libraryitems = parents[len(parents)-1].getDependants("libraryitem")

                        #add this component to each of the models for each of the libraryitems                        
                        for component in libraryitems.values():
                            component.getModel().onBeforeDeleteComponent(self, component.getComponentContext())

                else:
                    self.getModel().onBeforeDeleteComponent(self, componentContext)
        except:
            pass
        
        ###print "DEBUG: Component.onBeforeDelete: Completed"
        
    #---------------------------------------------------------------#

    def onBeforeShowIfChanged(self):
        """
            FINAL: Handle changing the showif expression for a component
        """

        #deregister the current show if dependancies
        if hasattr(self, "aq_parent"):
            self.deregisterDependencies()
        
        #show if has now changed
        self._v_ShowIfKERL = None
        
        ###print "DEBUG: Component.onBeforeShowIfChanged() Completed"
        
    #---------------------------------------------------------------#

    def onAfterShowIfChanged(self):
        """
            FINAL: Handle changing the showif expression for a component
        """

        #register the new show if dependancies
        if hasattr(self, "aq_parent"):
            self.registerDependencies()

        ###print "DEBUG: Component.onAfterShowIfChanged() Completed"
        
    #---------------------------------------------------------------#

    def getShowIfKERL(self):
        """
            FINAL: Returns a KERL Expression representing the
            optimized showif expression.
        """

        #do we have a KERL for the showif?
        if self._v_ShowIfKERL == None:
            self._v_ShowIFKERL = parseKERL(self.getShowIf())

        return self._v_ShowIFKERL
        
    #---------------------------------------------------------------#

    def getShowIfJavaScriptCondition(self, assessment, componentContext = None, page = None):
        """
            FINAL: Returns the JavaScript Guard (boolean expression) for the showif for the
            component.
        """
 
        #first get the transitive closure for the component
        transitiveClosure = getComponentShowIfTransitiveClosure(self, self.getModel(), assessment, componentContext)
 
        #get the page we are on (if not specified)
        if page is None:
            page = self.getPage()
 
        #let's optimize the showif (will return either 'always', 'never' or a kerl)
        optimizedShowIf = optimizeKERLForPageWithAssessment(transitiveClosure, assessment, page)

        #determine the javascript showif condition for the component
        javascriptCondition = toJavaScript(optimizedShowIf, self.getModel(componentContext), componentContext)
        
        #return an appropriate condition
        if javascriptCondition == "":
            return "true"
        else:
            return javascriptCondition
        
    #---------------------------------------------------------------#

    def getShowIfJavaScript(self, assessment, componentContext = None):
        """
            FINAL: Returns the JavaScript for the showif for the
            component.

            RETURNS: document.getElementById("span" + getIdentity() + ").style.display = (' + showifCondition + ' ? "inline" : "none");
        """
 
        #determine the showif condition for the component
        showifCondition = self.getShowIfJavaScriptCondition(assessment, componentContext)

        #return the javascript showif
        return "document.getElementById(\"span" + self.getIdentity(componentContext) + "\").style.display = (" + showifCondition + " ? \"inline\" : \"none\");"
        
    #---------------------------------------------------------------#

    def isShown(self, assessmentCache, componentContext = None):
        """
            FINAL: Evaluates (on the server-side) and returns if the
            component is to be shown based on the components
            show if expression.
        """

        #determine the showif condition for the component
        showIfExpression = getComponentShowIfTransitiveClosure(self, self.getModel(componentContext), assessmentCache, componentContext)

        #evaluate the expression using the provided assessment cache
        return evaluate(showIfExpression, assessmentCache, componentContext)
        
    #---------------------------------------------------------------#

    def onBeforePropertyChange(self, propertyName, newValue):
        "Handle when an object property is about to change (using the property editor)"

        #has the showif changed?
        hasShowIfChanged = propertyName == "showif" and hasattr(self, "showif")
        if hasShowIfChanged:
            self.onBeforeShowIfChanged()
        
    #---------------------------------------------------------------#

    def onAfterPropertyChange(self, propertyName, oldValue, newValue):
        "Handle when an object property has changed (using the property editor)"

        #has the showif changed?
        hasShowIfChanged = propertyName == "showif" and hasattr(self, "showif")
        if hasShowIfChanged:
            self.onAfterShowIfChanged()
                
    #---------------------------------------------------------------#

    def registerDependencies(self, componentContext = None):
        """
            Register this component as a dependant of each of it's
            dependancies.
        """

        #determine the indexed container to look up dependancies
        indexedContainer = None

        #is the component in a library?
        if self.isInLibrary():
            indexedContainer = self.getLibrary()
        else:
            indexedContainer = self.getModel(componentContext)

        #get the dependancies (from the showif kerl)
        dependencies = getDependencies(self.getShowIfKERL(), indexedContainer, componentContext)

        #register each of the dependencies in the showif kerl
        for component in dependencies.values():
            component.addDependant(self.getIdentity(componentContext), self, "showif")
                
    #---------------------------------------------------------------#

    def deregisterDependencies(self, componentContext = None):
        """
            Deregister this component as a dependant of each of it's
            dependancies.
        """
    
        ###print "DEBUG: Component.deregisterDependencies(): Commenced"        

        #determine the indexed container to look up dependancies
        indexedContainer = None

        #is the component in a library?
        if self.isInLibrary():
            indexedContainer = self.getLibrary()
        else:
            indexedContainer = self.getModel(componentContext)

        #get the dependancies (from the showif kerl)
        dependencies = getDependencies(self.getShowIfKERL(), indexedContainer, componentContext)

        #deregister each of the dependencies in the showif kerl
        for component in dependencies.values():
            component.removeDependant(self.getIdentity(componentContext), "showif")

        ###print "DEBUG: Component.deregisterDependencies(): Completed"
        
    #---------------------------------------------------------------#
    
    def isAssessible(self):
        "Return if the component supports stored assessment state"

        return isinstance(self, Assessible)
        
    #---------------------------------------------------------------#
    
    def isInLibrary(self):
        "Return if the component is in a library"

        #get the parent of the component
        parent = GenericObject.getParent(self)

        if isinstance(parent, Library):
            return true

        elif isinstance(parent, Model):
            return false

        elif parent.getIdentityType() == 'lib':
            return true
        
        elif isinstance(parent, Component):
            return parent.isInLibrary()
        
        else:
            return false

    #---------------------------------------------------------------#

    def getLibrary(self):
        """
            Returns the Library in which this component is located.
            Returns None if not in a library.
        """
        
        if isinstance(self.getParent(), Library):
            return self.getParent()
        elif isinstance(self.getParent(), Component):
            return self.getParent().getLibrary()
        else:
            return None
        
    #---------------------------------------------------------------#

    def getComponentProperty(self, identity, propertyName, assessmentCache = None):
        """
            Returns the value of the specified property, given the
            identity of the component (which may be used to lookup states) and
            an assessment cache.

            RETURNS: None if the property doesn't exist
        """

        if propertyName == "title":
            return self.getTitle()
        else:
            #see if the property can be gained using the Property Manager
            if self.hasProperty(propertyName):
                return self.getProperty(propertyName)
            else:
                return None

    #---------------------------------------------------------------#

    def isPreviewPermitted(self):
        """
            Return if the component is allowed to be previewed.
            
            By default all components can be previewed, some however
            should not be previewed. eg: Options whose display is 
            dependant on their parents.
        """
        
        return true
        
    #---------------------------------------------------------------#

    def addJavaScriptOnLoadStatements(self, onLoadStatementsDictionary, componentContext = None):
        """
            ABSTRACT: Adds the necessary onLoad statements to the 
            onLoadStatementsDictionary which is defined as {KEY -> string}.
            
            The KEY is used to determine which component generated 
            the statements.  The KEY may be used to determine if
            statements have already been generated for this type
            of component.

            The STRING is the actual javascript statements.
            
            NOTE: When overriding, this method should always be called
            from sub-classes.
        """
        
        #most components don't require onLoad statements
        pass
        
    #---------------------------------------------------------------#

    def addJavaScriptDeclarations(self, declarationsDictionary, componentContext = None):
        """
            ABSTRACT: Adds the necessary javascript declarations to 
            the declarationsDictionary which is defined as {KEY -> string}.
            
            The KEY is used to determine which component generated 
            the declarations.  The KEY may be used to determine if
            declarations have already been generated for this type
            of component.

            The STRING is the actual javascript declaration(s).

            NOTE: When overriding, this method should always be called
            from sub-classes.            
        """
        
        #most components don't require declarations
        pass
            
    #---------------------------------------------------------------#

    def getNextComponentSibling(self, assessmentCache = None):
        """
            Returns the next component sibling at this level
            
            RETURNS: None if this component is the last component sibling
        """
        
        #make sure the parent is a component container
        if self.getParent().isInstanceOf("ComponentContainer"):
            return self.getParent().getComponentSiblingAfterChild(self, assessmentCache)    
        
        else:
            return self.getNextSibling()
            
    #---------------------------------------------------------------#

    def getPreviousComponentSibling(self, assessmentCache = None):
        """
            Returns the previous component sibling at this level
            
            RETURNS: None if this component is the first component sibling
        """
        
        #make sure the parent is a component container
        if self.getParent().isInstanceOf("ComponentContainer"):
            return self.getParent().getComponentSiblingBeforeChild(self, assessmentCache)    
        
        else:
            return self.getPreviousSibling()
            
    #---------------------------------------------------------------#

    def getNextComponent(self, assessmentCache = None):
        """
            Returns the next component (doing a depth-first traversal)
            
            RETURNS: None if this component is the last component
        """
        
        #is this component a container?
        if self.isInstanceOf("ComponentContainer"):
            nextComponent = self.getFirstComponentChild(assessmentCache)
        else:
            nextComponent = None
            
        #do we have a next sibling?
        if nextComponent is None:
            nextComponent = self.getNextComponentSibling(assessmentCache)
        
        #do we have a next sibling?
        if nextComponent is None:
            #find a ComponentContainer parent with a next sibling
            parent = self.getParent()        
            while parent.isInstanceOf("ComponentContainer") and nextComponent is None:
                
                #get the next sibling of the parent
                nextComponent = parent.getNextComponentSibling(assessmentCache)
                
                #if the parent doesn't have a sibling, we need to keep looking up the tree
                if nextComponent is None:
                    parent = parent.getParent()

        return nextComponent
            
    #---------------------------------------------------------------#

    def getPreviousComponent(self, assessmentCache = None):
        """
            Returns the previous component (doing a depth-first traversal)
            
            RETURNS: None if this component is the last component
        """
        
        previousSibling = self.getPreviousComponentSibling(assessmentCache)
        
        #is there a previous sibling?
        if previousSibling is None:
            if self.getParent().isInstanceOf("ComponentContainer"):
                return self.getParent()
            else:
                return None
            
        elif previousSibling.isInstanceOf("ComponentContainer"):
            previousSibling = previousSibling.getLastDescendantComponentChild(assessmentCache)
          
        return previousSibling
        
    #---------------------------------------------------------------#

    def getPreviousComponentByInstanceOf(self, classInfoClassName, assessmentCache = None):
        """
            Attempts to locate a specified type of component in the 
            tree doing a depth-first traversal backwards up the tree
            using the provided assessment
        """

        #start with the previous component                
        previousComponent = self.getPreviousComponent(assessmentCache)
        
        #search until either we've run out of components OR we find the one we are after
        while previousComponent is not None and previousComponent.getClassInfo().getClassName() != classInfoClassName:
            previousComponent = previousComponent.getPreviousComponent(assessmentCache)
        
        return previousComponent
        
    #---------------------------------------------------------------#

    def testForwardTraversal(self, assessmentCache=None):
        """
            Test the forward traversal with the specified assessment cache
        """

        print "Forward from Node: %s"% self.getTitle()        
        nextComponent = self.getNextComponent(assessmentCache)

        while nextComponent is not None:
            print "Node: %s"% nextComponent.getTitle()        
            nextComponent = nextComponent.getNextComponent(assessmentCache)
        
    #---------------------------------------------------------------#

    def testBackwardTraversal(self, assessmentCache=None):
        """
            Test the backward traversal with the specified assessment cache
        """
        
        print "Backward from Node: %s"% self.getTitle()        
        previousComponent = self.getPreviousComponent(assessmentCache)

        while previousComponent is not None:
            print "Node: %s"% previousComponent.getTitle()        
            previousComponent = previousComponent.getPreviousComponent(assessmentCache)
        
    #---------------------------------------------------------------#
    
#####################################################################
## Class Initialisation
# (none)
